/*
 * Copyright (C) 2010-2014 Enrique Lara (enrique.posix2.lara@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
 * limitations under the License.
  */
package trs.gtfs.mapper;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

import trs.gtfs.model.TripTimePoint;

public abstract class GtfsRowMapper<T extends Object> {
    static final char QUOTE = '"';
    static final char COMMA = ',';

    public static class FieldDS<T extends Object> {
        final String name;
        int idx = -1;

        public FieldDS(String name) {
            this.name = name;
        }

        public void setIdx(int idx) {
            this.idx = idx;
        }

        public String getName() {
            return this.name;
        }

        public String toString() {
            return this.name + "(" + this.idx + ")";
        }

        public void processColumn(T o, String value) {
            //XXX ad parsing context
        }

        protected boolean toBoolean(String s) {
            return "1".equals(s);
        }

        private static final String DATE_FORMAT_YYYYMMDD = "yyyyMMyy";

        protected Date toDate(String s) {
            if (s != null) {
                if (s.length() == DATE_FORMAT_YYYYMMDD.length()) {
                    try {
                        SimpleDateFormat f = new SimpleDateFormat(DATE_FORMAT_YYYYMMDD);

                        return f.parse(s);
                    } catch (ParseException e) {
                        e.printStackTrace(); //XXX
                    }
                } else {
                    System.err.println("toDate FIXME " + s);
                }
            }

            return null;
        }

        protected TripTimePoint toTripTimePoint(String s) {
            if (s != null) {
                try {
                    return TripTimePoint.parseTripTimePoint(s);
                } catch (NumberFormatException e) {
                    e.printStackTrace(); //XXX
                }
            }

            return null;
        }

        protected Long toLong(String s) {
            if (s != null) {
                return Long.parseLong(s);
            }

            return Long.valueOf(0);
        }

        protected Integer toInteger(String s) {
            if (s != null) {
                return Integer.parseInt(s);
            }

            return Integer.valueOf(0);
        }

        protected Double toDouble(String s) {
            if (s != null) {
                return Double.parseDouble(s);
            }

            return Double.valueOf(0);
        }
    }

    protected final FieldDS<T>[] currentFields;

//	private int fieldCount = 0;
    @SuppressWarnings("unchecked")
    public GtfsRowMapper(String[] headers, FieldDS<T>[] allFields, FieldDS<T>[] currentFields) {
        this.currentFields = currentFields;

        // XXX add Validation.

//		FieldDS<T>[] allFields = getAllFields();
        Arrays.fill(currentFields, null);

        // set field indexes based on file header line, since optional fields
        // may not be present.
//        int fieldCount = 0;

        for (int i = 0; i < headers.length; i++) {
            String header = headers[i];

            for (FieldDS<T> field : allFields) {
                if (field.getName().equals(header)) {
                    field.setIdx(i);
//                    fieldCount++;

                    currentFields[i] = field;

                    continue;
                }
            }
        }

//		this.fieldCount = fieldCount;
    }

//	protected abstract FieldDS<T>[] getAllFields();
    protected abstract T newRow();

    public T processRow(String str, int line) {
        T o = newRow();

//		int idx = 0;
        int field = 0;
        int startIdx = 0;

        //XXX System.out.println("Line:" + line + ",str:" + str);
        processRow(o, line, str, field, startIdx);

        // XXX some sort of parsing context ? with errors?
        return o;
    }

    private void processRow(T o, int line, String str, int field, final int startIdx) {
        if (startIdx >= str.length()) {
            return;
        }

//		if(field >= fieldCount) {
//			System.out.println("Line:" + line + ", Ignoring Unknown Column Data:" + str.substring(startIdx));
//		}
        int idx = startIdx;

        String value = null;

        if (str.charAt(idx) == QUOTE) {
            // process quoted
            idx++;

            StringBuilder buff = new StringBuilder();

            boolean fieldDone = false;

            for (; ((idx < (str.length() - 1)) && !fieldDone); idx++) {
                if ((str.charAt(idx) == QUOTE) && (str.charAt(idx + 1) == QUOTE)) {
                    idx++;
                    buff.append(QUOTE);
                } else if ((str.charAt(idx) == QUOTE) && (str.charAt(idx + 1) == COMMA)) {
                    // field end.
                    fieldDone = true;
                } else {
                    buff.append(str.charAt(idx));
                }
            }

            value = buff.toString();
        } else {
            for (; (idx < str.length()) && (str.charAt(idx) != COMMA); idx++) {
            }

            if (startIdx < idx) {
                value = str.substring(startIdx, idx);
            }
        }

        if (currentFields[field] != null) {
            //XXX System.out.println("INCLUDE: Line:" + line + ", Field:" + field + ", startIdx:" + startIdx + ", endIdx:" + idx + ", value:" + value);
            currentFields[field].processColumn(o, value);
        } else {
            //FIXME System.out.println(o.getClass() + ":IGNORE: Line:" + line + ", Field:" + field + ", startIdx:" + startIdx + ", endIdx:" + idx + ", value:" + value);
        }

        processRow(o, line, str, field + 1, idx + 1);
    }

    public String toString() {
        return "FieldMapper: fields=" + Arrays.toString(this.currentFields);
    }
}
